﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DotSpatial.Topology;
using DotSpatial.Data;
using DotSpatial.Projections;

namespace GeostatisticalTool.Classes
{
    public class AnisotropyValues : ISpatialContinuityData,ICloneable
    {
        private SortedList<int, double> distancesAnisotropic = null;
        private SortedList<int, double> semivarianceAnisotropic = null;
        private SortedList<int, double> covarianceAnisotropic = null;

        private SpatialContinuity spatial;
        private double majorRange;
        private double minorRange;


        private double bandWidth;
        private double tolerance; 
        private double azimuth;
        private double angdip2;
        public double BandWidth
        {
            get { return bandWidth; }
            set { bandWidth = value;
            CalculateListValues();
            OnChangedAnisotropyParameter(EventArgs.Empty);
            }
        }
        public double Tolerance
        {
            get { return Utils.ToDeg(this.tolerance); }
            set
            {
                this.tolerance = Utils.ToRad(value);
            CalculateListValues();
            OnChangedAnisotropyParameter(EventArgs.Empty);
            }
        }
        public double Azimuth
        {
            get { return Utils.ToDeg(this.azimuth); }
            set
            {
                this.azimuth = Utils.ToRad(value);
            CalculateListValues();
            OnChangedAnisotropyParameter(EventArgs.Empty);

            }
        }

        public double AngDip2
        {
            get { return Utils.ToDeg(this.angdip2); }
            set
            {
                this.angdip2 = Utils.ToRad(value);
                CalculateListValues();
                OnChangedAnisotropyParameter(EventArgs.Empty);

            }
        }
      
      
        public void SetDirectionParameters(double bandWidth, double tolerance, double azimuth, double angdip)
        {
            this.bandWidth = bandWidth;
            this.tolerance = Utils.ToRad(tolerance);
            this.azimuth = Utils.ToRad(azimuth);
            this.angdip2 = Utils.ToRad(angdip);
            CalculateListValues();
            OnChangedAnisotropyParameter(EventArgs.Empty);
        }

        public double MinorRange
        {
            get { return minorRange; }
            set { minorRange = value; }
        }

        public double MajorRange
        {
            get { return majorRange; }
            set { majorRange = value; }
        }




        public event EventHandler ChangedAnisotropyParameter;
        

        protected virtual void OnChangedAnisotropyParameter(EventArgs e)
        {
            if (ChangedAnisotropyParameter != null)
                ChangedAnisotropyParameter(this, e);
        }

        public AnisotropyValues(SpatialContinuity spatial) 
        {
            this.spatial = spatial;
            this.azimuth = Utils.ToRad(0);
            this.angdip2 = Utils.ToRad(0);
            this.tolerance = Utils.ToRad(45);
            this.bandWidth = 3;
            CalculateListValues();
        }


        private Coordinate GetCoordinatesOfColRow(int row, int col)
        {
            if (this.spatial.LagValue == null)
                return null;

            double size = this.spatial.NumberOfLags * 2 * this.spatial.LagSize;
            Coordinate center;
                center = new Coordinate(500000 + (size / 2), size / 2);


            double initialcol = this.spatial.LagSize / 2;
            double initialrow = this.spatial.LagSize / 2;
            double len = this.spatial.NumberOfLags * this.spatial.LagSize;
            double x, y;

            x = center.X +((len) - initialcol - ((col) * this.spatial.LagSize))*-1;
            y = center.Y + (len) - initialcol - ((row) * this.spatial.LagSize);

            return new Coordinate(x,y);

        }
        private double GetAzimuthGrid(double x, double y)
        {
            double dx = (x);
            double dy = (y);
            if (dx == 0 && dy > 0) return 0;
            if (dx == 0 && dy < 0) return (Math.PI);
            if (dx > 0 && dy == 0) return (Math.PI / 2);
            if (dx < 0 && dy == 0) return (3 * Math.PI / 2);
            if (dx > 0 && dy > 0) return (Math.Atan(dx / dy));
            if (dx > 0 && dy < 0) return (Math.PI + Math.Atan(dx / dy));
            if (dx < 0 && dy < 0) return ((Math.PI) + Math.Atan(dx / dy));
            if (dx < 0 && dy > 0) return ((2 * Math.PI) + Math.Atan(dx / dy));
            return 0;
        }


        public IFeatureSet ZoneSearch()
        {
            if (this.spatial.LagValue == null)
                return null;

            FeatureSet border = new FeatureSet(DotSpatial.Topology.FeatureType.Polygon);
            border.Projection = KnownCoordinateSystems.Projected.UtmWgs1984.WGS1984UTMZone10N;
            border.Features.Add(CreatePolygonData(true));
            return border;
        }


        private void CalculateListValues()
        {
            if (this.spatial.LagValue == null)
                return;

            distancesAnisotropic = new SortedList<int, double>();
            semivarianceAnisotropic = new SortedList<int, double>();
            covarianceAnisotropic = new SortedList<int, double>();
            IGeometry pol = CreatePolygonData(true);

            double size = this.spatial.LagValue.numLags * 2 * this.spatial.LagValue.lagSize;

            int nr = (this.spatial.LagValue.numLags * 2);
            int nc = (this.spatial.LagValue.numLags * 2);
            int i = 0;
            Random r = new Random(Convert.ToInt32(DateTime.Now.ToOADate() * 10000));

            for (int row = 0; row < nr; row++)
            {
                for (int col = 0; col < nc; col++)
                {
                    
                    Coordinate p = GetCoordinatesOfColRow(row,col);
                    DotSpatial.Topology.Point point = new Point(p);
                    if (pol.Contains(point) && this.spatial.SemivarianceOfGrid[row, col].num > 0 && this.spatial.GetDistanceGrid(row, col) <= (this.spatial.LagValue.numLags * this.spatial.LagValue.lagSize))
                    {
                        // distancesA.Add(i, spatDist[row, col].Mean);
                        double v = r.NextDouble()/10000;

                        distancesAnisotropic.Add(i, this.spatial.GetDistanceGrid(row, col)+v);
                        semivarianceAnisotropic.Add(i, this.spatial.SemivarianceOfGrid[row, col].Mean);
                        covarianceAnisotropic.Add(i, this.spatial.CovarianceOfGrid[row, col].Result());
                        i++;
                    }
                }
            }
        }
        
       private IGeometry CreatePolygonData(bool type)
        {

            double size = this.spatial.NumberOfLags * 2 * this.spatial.LagSize;
            Coordinate center;
           if (type)
           center = new Coordinate(500000 + (size / 2), size / 2);
           else
           center = new Coordinate(0,0);

            Coordinate[] array = new Coordinate[6];

            array[0] = center;
            array[1] = this.spatial.AzimuthDist(center, azimuth + tolerance, bandWidth*this.spatial.LagSize);
            array[2] = this.spatial.AzimuthDist(array[1], azimuth, this.spatial.LagValue.numLags * this.spatial.LagSize);
            array[3] = this.spatial.AzimuthDist(array[2], azimuth + Math.PI + Math.PI/2, Math.Sin(tolerance) * (bandWidth * this.spatial.LagSize) * 2);
            array[4] = this.spatial.AzimuthDist(array[3], azimuth + Math.PI, this.spatial.LagValue.numLags * this.spatial.LagSize);
            array[5] = center;
            LinearRing shell = new LinearRing(array);
            Polygon poly = new Polygon(shell);
            return poly;

        }


       public double[] GetDistancesValues()
       {
           return this.distancesAnisotropic.Values.ToArray();
       }

       public double[] GetSemivariancesValues()
       {
           return this.semivarianceAnisotropic.Values.ToArray();
       }


       public Stat GetDistanceStatistic()
       {
           return this.spatial.DistanceStatistic;
       }


       public Stat GetSemivarianceStatistic()
       {
           return this.spatial.SemivarianceStatistic;
       }

       public Lag GetLag()
       {
           return this.spatial.LagValue;
       }

       public object Clone()
       {
           return new AnisotropyValues(this.spatial);
       }
    }
}
